package main;

import java.util.ArrayList;

/**
 * Takes tokens as input and chekcs for syntax as well as creates assembly code.
 * 
 */
public class Parser {

	/**
	 * Tokenizes input file.
	 */
	private Tokenizer tokenizer;

	/**
	 * List of symbols from the tokenizer.
	 */
	private ArrayList<Symbol> symbols = new ArrayList<Symbol>();
	/**
	 * List of tokens from the input file.
	 */
	private ArrayList<String> tokens = new ArrayList<String>();
	/**
	 * Position used throughout the class to keep track of where the parser is in the tokens list.
	 */
	private int pos;
	/**
	 * Segment with the assembly code.
	 */
	public Segment seg;
	/**
	 * The symbol table is generated in this class.
	 */
	public ArrayList<Symbol> symbolTable = new ArrayList<Symbol>();
	/**
	 * The Symbol table with the name, id, an overflow Boolean value.
	 */
	private int level;
	/**
	 * Constructor with filename of input file. Tokenizes the input file
	 * and gets symbols for all tokens.
	 * @param fileName file to open and parse.
	 */
	public Parser(String fileName) {
		tokenizer = new Tokenizer(fileName);
		tokenizer.tokenize();
		tokens = tokenizer.getTokens();
		seg = new Segment();
		symbols = tokenizer.getSymbols();
		pos = 0;
		level = 0;
	}

	/**
	 * Method that is called to parse input all other methods are called as needed from here.
	 */
	public void parseProgram() {
		while(pos < tokens.size()) {
			parseStatement();
		}
		seg.gen(Segment.end, 0, 0);
	}

	/**
	 * Parses a statement consisting of a assignment or conditional
	 * or loop
	 */
	private void parseStatement() {
		//assignment || conditional || loop || IO
		// if is a loop or if else structure
		if (ReservedSymbols.containsConditional(tokens.get(pos))) {
			if (tokens.get(pos).equals(ReservedSymbols.S_WHILE)) {
				//generate code
				int codePos = seg.nextInstrAdr();
				pos++;
				//now parse the loop
				parseLoop();
				//update jump address
				seg.gen(Segment.jmp, 0, codePos);
			} else {
				if (tokens.get(pos).equals(ReservedSymbols.S_IF)) {
					pos++;
					// parse if else
					parseConditional();
					if (pos < tokens.size() && tokens.get(pos).equals(ReservedSymbols.S_ELSE)) {
						// record next spot in the assembly code to be generated
						int ifPos = seg.nextInstrAdr();
						//add the jump statement after the if
						seg.gen(Segment.jmp, 0, 0);
						pos++;
						//parse the else and add the address to the jmp after the else is generated
						parseConditional();
						seg.setAddrField(ifPos, seg.nextInstrAdr());
					}
				} else {
					error("Else statements must be coupled with if");
				}
			}
		}
		// is input or output
		else if (ReservedSymbols.containsIO(tokens.get(pos))) {
			parseIO();
		}
		// else if is a assignment statement with equals
		else if (Character.isLetter(tokens.get(pos).charAt(0))) {
			parseAssignment();
		} 
		else {
			error("if, loop or assignment statement");
		}
	}

	/**
	 * Parses an assignment with numbers and expressions.
	 */
	private void parseAssignment() {
		//identifier := expression ;
		int oldPos = pos;
		//if not symbol then add to symbol table
		if(!isSymbol(tokens.get(pos))) {
				parseIdentifier();
		}
		else { //otherwise do nothing
			pos++;
		}
		if (tokens.get(pos).equals(ReservedSymbols.S_EQUALS_IDENTIFIER)) {
			pos++;
			// if assignment is letters then is an identifier
			if (Character.isLetter(tokens.get(pos).charAt(0))) {
				parseExpression();
				if (tokens.get(pos).equals(ReservedSymbols.S_EOL_IDENTIFIER)) {
					seg.gen(Segment.sto, 0, getSymbolPos(oldPos));
					pos++;
				} else {
					error("; at end of assignment");
				}
			}
			// assignment is a digit
			else if (Character.isDigit(tokens.get(pos).charAt(0))) {
				parseDigit();
				// while more operations
				while (!tokens.get(pos).equals(ReservedSymbols.S_EOL_IDENTIFIER)) {
					parseOperator();
				}
				//store the reslut in the symbol table
				seg.gen(Segment.sto, 0, getSymbolPos(oldPos));
				if (tokens.get(pos).equals(ReservedSymbols.S_EOL_IDENTIFIER)) {
					pos++;
				} else {
					error("; at end of assignment");
				}
			} else { // is negative symbol
				parseOperator();
				if (tokens.get(pos).equals(ReservedSymbols.S_EOL_IDENTIFIER)) {
					pos++;
				} else {
					error("; at end of assignment");
				}
				seg.gen(Segment.sto, 0, getSymbolPos(oldPos));
			}
		} else {
			error("Expected equals identifier after assignment");
		}
	}

	/**
	 * Parses a if else structure and creates jumps for assembly code.
	 */
	private void parseConditional() {
		// if conditional start then expression follows
		if (tokens.get(pos).equals(ReservedSymbols.S_CONDITIONAL_START_IDENTIFIER)) {
			pos++;
			parseExpression();
			//after expression should be conditional end
			if (tokens.get(pos).equals(ReservedSymbols.S_CONDITIONAL_END_IDENTIFIER)) {
				pos++;
				int codePos = seg.nextInstrAdr();
				seg.gen(Segment.jpc, 0, 0);
				//after conditional start is seen it is followed by a statement
				if (tokens.get(pos).equals(ReservedSymbols.S_BLOCK_START_IDENTIFIER)) {
					pos++;
					level++;
					parseStatement();
					//statement needs to end with a } symbol
					while (!tokens.get(pos).equals(ReservedSymbols.S_BLOCK_END_IDENTIFIER)) {
						parseStatement();
					}
					pos++;
					level--;
				} else {
					error("{ to start conditional statement");
				}
//				seg.gen(Segment.jmp, 0, codePos);
				if ( pos < tokens.size() && tokens.get(pos).equals(ReservedSymbols.S_ELSE)) {
					seg.setAddrField(codePos, seg.nextInstrAdr() + 1);
				}
				else {
					seg.setAddrField(codePos, seg.nextInstrAdr());
				}
			} else {
				error("Expected ) but was missing");
			}
		} else { //parse else statement after if
			if (tokens.get(pos-1).equals(ReservedSymbols.S_ELSE)) {
				if (tokens.get(pos).equals(ReservedSymbols.S_BLOCK_START_IDENTIFIER)) {
					pos++;
					level++;
					parseStatement();
					//parse all statements before block end
					while (!tokens.get(pos).equals(ReservedSymbols.S_BLOCK_END_IDENTIFIER)) {
						parseStatement();
					}
					pos++;
					level--;
				} else {
					error("{ to start conditional statement");
				}
			} else {
				error("Expected ( before expression");
			}
		}
	}

	/**
	 * Parses a loop statement and creates conditional jumps and a jump at
	 * the end.
	 */
	private void parseLoop() {
		if (tokens.get(pos).equals(ReservedSymbols.S_CONDITIONAL_START_IDENTIFIER)) {
			int ifPos = pos;
			pos++;
			parseExpression();
			// get position to jump back to and create jump conditional
			int codePos = seg.nextInstrAdr();
			seg.gen(Segment.jpc, 0, 0);
			if (tokens.get(pos).equals(ReservedSymbols.S_CONDITIONAL_END_IDENTIFIER)) {
				pos++;
				//after conditional start is seen it is followed by a statement
				if (tokens.get(pos).equals(ReservedSymbols.S_BLOCK_START_IDENTIFIER)) {
					pos++;
					level++;
					parseStatement();
					//allow for multiple statements
					while (!tokens.get(pos).equals(ReservedSymbols.S_BLOCK_END_IDENTIFIER)) {
						parseStatement();
					}
					pos++;
					level--;
				} else {
					error("{ to start conditional statement");
				}
			} else {
				error("Expected ) but was missing");
			}
			//set the jump conditional address
			seg.setAddrField(codePos, seg.nextInstrAdr()+1);
		} else {
			error("Expected ( before expression in loop");
		}
	}

	/**
	 * Parses a term which is a sequence of letters or numbers.
	 */
	private void parseTerm() {
		//idenditifer || number || ( expression )
		if (Character.isDigit(tokens.get(pos).charAt(0))) {
			parseDigit();
		}
		else if (Character.isLetter(tokens.get(pos).charAt(0))) {
			parseIdentifier();
		}
		else if (tokens.get(pos).equals(ReservedSymbols.S_CONDITIONAL_START_IDENTIFIER)) {
			pos++;
			parseExpression();
		} else {
			error("Expected letter or number or expression");
		}
	}

	/**
	 * Parses an expression.
	 */
	private void parseExpression() {
//		//term || term operator expression oper... ect
		if (tokens.get(pos).equals(ReservedSymbols.S_NEGATIVE)) {
			parseOperator();
		} else {
			parseTerm();
		}
		if (!tokens.get(pos-2).equals(ReservedSymbols.S_CONDITIONAL_START_IDENTIFIER) && !tokens.get(pos-2).equals(ReservedSymbols.S_CONDITIONAL_START_IDENTIFIER)) {
			while (pos + 1 < tokens.size() && !(tokens.get(pos).equals(ReservedSymbols.S_EOL_IDENTIFIER)) && symbols.get(pos).symtype != Symbol.I_NEGATIVE) {
				parseOperator();
			}
		} else {
			parseOperator();
			//TODO
//			parseIdentifier();
		}
	}

	/**
	 * Parse input output statements.
	 */
	private void parseIO() {
		//input or output followed by expression
		pos++;
		// input output must be a symbol in the symbol table
		if (Character.isLetter(tokens.get(pos).charAt(0)) && tokens.get(pos+1).equals(ReservedSymbols.S_EOL_IDENTIFIER)) {
			if (tokens.get(pos-1).equals(ReservedSymbols.S_INPUT)) {
				parseExpression();
				seg.gen(Segment.opRead, 0, 0);
				seg.gen(Segment.sto, 0, getSymbolPos(pos-1));
			}
			else {
				seg.gen(Segment.opWrite, 0, getSymbolPos(pos));
				pos++;
			}
			if (tokens.get(pos).equals(ReservedSymbols.S_EOL_IDENTIFIER)) {
				pos++;
			} else {
				error("; at end of input/output expression");
			}
		} else {
			error("expression after input/output and no operations allowed");
		}
	}
	/**
	 * Parses an operator +, -, /, ==, != ect...
	 */
	private void parseOperator() {
		// any operator
		if (ReservedSymbols.containsOperation(tokens.get(pos))) {
			//plus, minus, times, division, gt, lt, ge, le, noteq
			int i = symbols.get(pos).symtype;
			switch (i) {
			case Symbol.I_PLUS:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opPlus, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opPlus, 0, getSymbolPos(pos));
					pos++;
					
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opPlus, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opPlus, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_MINUS:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opMinus, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opMinus, 0, getSymbolPos(pos));
					pos++;
					
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opMinus, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opMinus, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_MULTIPLY:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opTimes, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opTimes, 0, getSymbolPos(pos));
					pos++;
					
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opTimes, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opTimes, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_DIVIDE:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opDiv, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opDiv, 0, getSymbolPos(pos));
					pos++;
					
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opDiv, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opDiv, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
				// greater than symbol
			case Symbol.I_GTSYMBOL:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opGreater, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					//TODO
					parseIdentifier();
					seg.gen(Segment.opGreater, 0, getSymbolPos(pos-1));
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opGreater, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opGreater, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_GTESYMBOL:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opGreaterEqual, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					parseIdentifier();
					seg.gen(Segment.opGreaterEqual, 0, getSymbolPos(pos-1));
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opGreaterEqual, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opGreaterEqual, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_LTSYMBOL:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opLess, 1, symbols.get(pos).num);
					pos++;
				}
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opLess, 0, getSymbolPos(pos));
					pos++;
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opLess, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opLess, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_LTESYMBOL:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opLessEqual, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opLessEqual, 0, getSymbolPos(pos));
					pos++;
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opLessEqual, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opLessEqual, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_NOTEQL:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opNotEqual, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opNotEqual, 0, getSymbolPos(pos));
					pos++;
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opNotEqual, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opNotEqual, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_EQUIVSYMBOL:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opEqual, 1, symbols.get(pos).num);
					pos++;
				} 
				else if (Character.isLetter(tokens.get(pos).charAt(0))) {
					seg.gen(Segment.opEqual, 0, getSymbolPos(pos));
					pos++;
				} else {
					parseOperator();
					if (Character.isLetter(tokens.get(pos-1).charAt(0))) {
						parseIdentifier();
						seg.gen(Segment.opEqual, 0, getSymbolPos(pos-2));
						pos--;
					}
					else {
						seg.gen(Segment.opEqual, 1, -Integer.parseInt(tokens.get(pos-1)));
					}
				}
				break;
			case Symbol.I_NEGATIVE:
				pos++;
				if (Character.isDigit(tokens.get(pos).charAt(0))) {
					parseDigitNegative();
				} else {
					parseIdentifier();
					seg.gen(Segment.opUnaryMinus, 0, getSymbolPos(pos-1));
				}
				break;
			default: error("a proper symbol");
			}
		} else {
			error("operator");
		}
	}

	/**
	 * Checks to make sure it is a digit and generates a literal statement.
	 */
	private void parseDigit() {
		if (Character.isDigit(tokens.get(pos).charAt(0))) {
			//if is digit then load it to the stack
			seg.gen(Segment.lit, 0, Integer.parseInt(tokens.get(pos)));
			pos++;
		} else {
			error("Digits must start with number");
		}
	}

	/**
	 * Determines weather the identifier is a symbol if so then generates load
	 * if not it adds the identifier to the symbol table.
	 */
	private void parseIdentifier() {
		//check to see if its in symbol table otherwise add it
		if (isSymbol(tokens.get(pos))) {
			//add symbol and position in symbolTable
			seg.gen(Segment.lod, 0, getSymbolPos(pos));
			pos++;
		} else {
			//if not in symbol table add it.
			symbolTable.add(symbols.get(pos));
			pos++;
		}
	}

	/**
	 * Parses a negative digit by generating a negative literal.
	 */
	private void parseDigitNegative() {
		if (Character.isDigit(tokens.get(pos).charAt(0))) {
			seg.gen(Segment.lit, 0, -Integer.parseInt(tokens.get(pos)));
			pos++;
		} else {
			error("Digits must start with number");
		}
		
	}

	/**
	 * Used to print out when errors are found in syntax. It also prints the
	 * stack track. Exits.
	 * @param errnum Number of the error
	 */
	void error(String token) {
		System.out.println("*** error in file Expected " + token + "***\n");
		Thread.dumpStack();
		System.exit(0);
		
	}

	/**
	 * Determines weather the object is in the symbol table.
	 * @param sym The string to check for in the symbol table.
	 * @return true if symbol is in table and false otherwise
	 */
	private boolean isSymbol(String sym) {
		int i = 0;
		while (i < symbolTable.size()) {
			if (symbolTable.get(i).id.equals(sym)) {
				return true;
			}
			i++;
		}
		return false;
	}

	/**
	 * Gets the position of the given symbol in the table
	 * @param position The current position of the token array
	 * @return symbol position of symbol or -1 if error
	 */
	private int getSymbolPos(int position) {
		int i = 0;
		while (i < symbolTable.size()) {
			if (symbolTable.get(i).id.equals(tokens.get(position))) {
				return i;
			}
			i++;
		}
		return -1;
	}

	/**
	 * Used for debugging purposes prints a readable symbol table.
	 */
	public void printSymbolTable() {
		int i = 0;
		Machine.out.println("SYMBOL TABLE");
		Machine.out.println("\t\tSymbolname \tID \toverflow");
//		System.out.println("SYMBOL TABLE");
//		System.out.println("\t\tSymbolname \tID \toverflow");
		while(i < symbolTable.size()) {
//			System.out.println("Symbol" + i + ":\t" + symbolTable.get(i).id + "\t\t" + symbolTable.get(i).num + "\t" + symbolTable.get(i).overflow);
			Machine.out.write("Symbol" + i + ":\t" + symbolTable.get(i).id + "\t\t" + symbolTable.get(i).num + "\t" + symbolTable.get(i).overflow + "\n");
			i++;
		}
//		System.out.println("\n\n");
		Machine.out.println("\n\n");
	}
	
	/**
	 * Prints the list of tokens to the output file.
	 */
	public void printTokens() {
		int count = 0;
		Machine.out.println("LIST OF RELAVENT TOKENS");
//		System.out.println("LIST OF RELAVENT TOKENS");

		while (tokens.size() > count) {
//			System.out.println(tokens.get(count));
			Machine.out.println(tokens.get(count));
			count++;
		}
		Machine.out.println("\n\n");
//		System.out.println("\n\n");

	}
}
